Components with hierarchy#

image0

You can define components Parametric cells (waveguides, bends, couplers) with basic input parameters (width, length, radius …) and reuse the PCells in more complex PCells.

[1]:
from functools import partial
import toolz

from gdsfactory.typings import ComponentSpec

import gdsfactory as gf
from gdsfactory.generic_tech import get_generic_pdk

gf.config.rich_output()
PDK = get_generic_pdk()
PDK.activate()
2023-02-20 17:48:53.474 | INFO     | gdsfactory.config:<module>:50 - Load '/home/runner/work/gdsfactory/gdsfactory/gdsfactory' 6.43.1
2023-02-20 17:48:54.378 | INFO     | gdsfactory.technology.layer_views:__init__:785 - Importing LayerViews from YAML file: /home/runner/work/gdsfactory/gdsfactory/gdsfactory/generic_tech/layer_views.yaml.
2023-02-20 17:48:54.385 | INFO     | gdsfactory.pdk:activate:206 - 'generic' PDK is now active
[2]:
@gf.cell
def bend_with_straight(
    bend: ComponentSpec = gf.components.bend_euler,
    straight: ComponentSpec = gf.components.straight,
) -> gf.Component:
    c = gf.Component()
    b = gf.get_component(bend)
    s = gf.get_component(straight)

    bref = c << b
    sref = c << s

    sref.connect("o2", bref.ports["o2"])
    c.info["length"] = b.info["length"] + s.info["length"]
    return c


c = bend_with_straight()
print(c.metadata['info']['length'])
c
26.637
bend_with_straight: uid c53b8465, ports [], references ['bend_euler_1', 'straight_1'], 0 polygons

ComponentSpec#

When defining a Parametric cell you can use other ComponentSpec as an argument. It can be a:

  1. string: function name of a cell registered on the active PDK. "bend_circular"

  2. dict: dict(component='bend_circular', settings=dict(radius=20))

  3. function: Using functools.partial you can customize the default parameters of a function.

1. string#

[3]:
c = bend_with_straight(bend="bend_circular")
c
bend_with_straight_bend_840d811a: uid b1db8557, ports [], references ['bend_circular_1', 'straight_1'], 0 polygons

2. dict#

Lets customize the functions that we pass. For example, we want to increase the radius of the bend from the default 10um to 20um.

[4]:
c = bend_with_straight(bend=dict(component="bend_circular", settings=dict(radius=20)))
c
bend_with_straight_113977eb: uid 89dc019c, ports [], references ['bend_circular_1', 'straight_1'], 0 polygons

3. function#

Partial lets you define different default parameters for a function, so you can modify the settings for the child cells.

[5]:
c = bend_with_straight(bend=gf.partial(gf.components.bend_circular, radius=30))
c
bend_with_straight_e172a9e6: uid b55cf7c6, ports [], references ['bend_circular_1', 'straight_1'], 0 polygons
[6]:
bend20 = partial(gf.components.bend_circular, radius=20)
b = bend20()
b
bend_circular_radius20: uid f28bc79d, ports ['o1', 'o2'], references [], 1 polygons
[7]:
type(bend20)
<class 'functools.partial'>
[8]:
bend20.func.__name__
'bend_circular'
[9]:
bend20.keywords
{'radius': 20}
[10]:
b = bend_with_straight(bend=bend20)
print(b.metadata['info']['length'])
b
41.416
bend_with_straight_e917218d: uid 4319af97, ports [], references ['bend_circular_1', 'straight_1'], 0 polygons
[11]:
# You can still modify the bend to have any bend radius
b3 = bend20(radius=10)
b3
bend_circular_radius10: uid c25a94aa, ports ['o1', 'o2'], references [], 1 polygons

PDK custom fab#

You can define a new PDK by creating function that customize partial parameters of the generic functions.

Lets say that this PDK uses layer (41, 0) for the pads (instead of the layer used in the generic pad function).

You can also access functools.partial from gf.partial

[12]:
pad = gf.partial(gf.components.pad, layer=(41, 0))
[13]:
c = pad()
c
pad_layer41__0: uid 115f5d81, ports ['e1', 'e2', 'e3', 'e4', 'pad'], references ['compass_1'], 0 polygons

Composing functions#

You can combine more complex functions out of smaller functions.

Lets say that we want to add tapers and grating couplers to a wide waveguide.

[14]:
c1 = gf.components.straight()
c1
straight: uid 35b0a132, ports ['o1', 'o2'], references [], 1 polygons
[15]:
straight_wide = gf.partial(gf.components.straight, width=3)
c3 = straight_wide()
c3
straight_width3: uid 21b1940e, ports ['o1', 'o2'], references [], 1 polygons
[16]:
c1 = gf.components.straight(width=3)
c1
straight_width3: uid 21b1940e, ports ['o1', 'o2'], references [], 1 polygons
[17]:
c2 = gf.add_tapers(c1)
c2
straight_width3_add_tap_9ae57f22: uid 5b1d4496, ports ['o1', 'o2'], references ['taper_1', 'taper_2', 'straight_1'], 0 polygons
[18]:
c2.metadata_child['changed']  # You can still access the child metadata
{'width': 3}
[19]:
c3 = gf.routing.add_fiber_array(c2, with_loopback=False)
c3
straight_width3_add_tap_c79a46d8: uid def6f0de, ports ['opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o1', 'opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o2'], references ['bend_euler_1', 'straight_1', 'straight_2', 'bend_euler_2', 'straight_3', 'straight_4', 'grating_coupler_elliptical_trenches_1', 'grating_coupler_elliptical_trenches_2', 'add_tapers_1'], 0 polygons
[20]:
c3.metadata_child['changed']  # You can still access the child metadata
{'width': 3}

Lets do it with a single step thanks to toolz.pipe

[21]:
add_fiber_array = gf.partial(gf.routing.add_fiber_array, with_loopback=False)
add_tapers = gf.add_tapers

# pipe is more readable than the equivalent add_fiber_array(add_tapers(c1))
c3 = toolz.pipe(c1, add_tapers, add_fiber_array)
c3
straight_width3_add_tap_c79a46d8: uid def6f0de, ports ['opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o1', 'opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o2'], references ['bend_euler_1', 'straight_1', 'straight_2', 'bend_euler_2', 'straight_3', 'straight_4', 'grating_coupler_elliptical_trenches_1', 'grating_coupler_elliptical_trenches_2', 'add_tapers_1'], 0 polygons

we can even combine add_tapers and add_fiber_array thanks to toolz.compose or toolz.compose

For example:

[22]:
add_tapers_fiber_array = toolz.compose_left(add_tapers, add_fiber_array)
c4 = add_tapers_fiber_array(c1)
c4
straight_width3_add_tap_c79a46d8: uid def6f0de, ports ['opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o1', 'opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o2'], references ['bend_euler_1', 'straight_1', 'straight_2', 'bend_euler_2', 'straight_3', 'straight_4', 'grating_coupler_elliptical_trenches_1', 'grating_coupler_elliptical_trenches_2', 'add_tapers_1'], 0 polygons

is equivalent to

[23]:
c5 = add_fiber_array(add_tapers(c1))
c5
straight_width3_add_tap_c79a46d8: uid def6f0de, ports ['opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o1', 'opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o2'], references ['bend_euler_1', 'straight_1', 'straight_2', 'bend_euler_2', 'straight_3', 'straight_4', 'grating_coupler_elliptical_trenches_1', 'grating_coupler_elliptical_trenches_2', 'add_tapers_1'], 0 polygons

as well as equivalent to

[24]:
add_tapers_fiber_array = toolz.compose(add_fiber_array, add_tapers)
c6 = add_tapers_fiber_array(c1)
c6
straight_width3_add_tap_c79a46d8: uid def6f0de, ports ['opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o1', 'opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o2'], references ['bend_euler_1', 'straight_1', 'straight_2', 'bend_euler_2', 'straight_3', 'straight_4', 'grating_coupler_elliptical_trenches_1', 'grating_coupler_elliptical_trenches_2', 'add_tapers_1'], 0 polygons

or

[25]:
c7 = toolz.pipe(c1, add_tapers, add_fiber_array)
c7
straight_width3_add_tap_c79a46d8: uid def6f0de, ports ['opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o1', 'opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o2'], references ['bend_euler_1', 'straight_1', 'straight_2', 'bend_euler_2', 'straight_3', 'straight_4', 'grating_coupler_elliptical_trenches_1', 'grating_coupler_elliptical_trenches_2', 'add_tapers_1'], 0 polygons
[26]:
c7.metadata_child['changed']  # You can still access the child metadata
{'width': 3}
[27]:
c7.metadata['child']['child']['name']
'straight_width3'
[28]:
c7.metadata['child']['child']['function_name']
'straight'
[29]:
c7.metadata['changed'].keys()
dict_keys(['component', 'with_loopback'])